package com.mic.gae.service.impl;

import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.users.UserService;
import com.google.appengine.repackaged.com.google.common.util.Base64;
import com.google.inject.Inject;
import com.mic.gae.dto.BuildDTO;
import com.mic.gae.dto.BuildsDTO;
import com.mic.gae.dto.ConsoleContentDTO;
import com.mic.gae.dto.ConsoleDTO;
import com.mic.gae.dto.ProjectDTO;
import com.mic.gae.dto.ProjectsDTO;
import com.mic.gae.dto.ServerDTO;
import com.mic.gae.service.ContiniousIntegrationService;

/**
 * 
 * @author michael
 * 
 */
public class HudsonContiniousIntegrationService implements ContiniousIntegrationService {

	private DatastoreService datastoreService;
	private URLFetchService urlFetchService;

	/**
	 * 
	 * @param datastoreService
	 * @param urlFetchService
	 * @param userService
	 */
	@Inject
	public HudsonContiniousIntegrationService(DatastoreService datastoreService, URLFetchService urlFetchService,
	        UserService userService) {
		this.datastoreService = datastoreService;
		this.urlFetchService = urlFetchService;
	}

	/**
	 * 
	 */
	@Override
	public ServerDTO addServer(ServerDTO serverDTO) {
		Entity serverEntity = toEntity(serverDTO);

		Key serverKey = this.datastoreService.put(serverEntity);
		serverDTO.setKey(serverKey.getId());

		return serverDTO;
	}

	/**
	 * 
	 */
	@Override
	public Long deleteServer(String identity, Long serverId) {
		Key serverKey = KeyFactory.createKey("CiServer", serverId.longValue());

		Query query = new Query("CiServer");
		query.addFilter("federatedIdentity", FilterOperator.EQUAL, identity).addFilter("__key__", FilterOperator.EQUAL,
		        serverKey);
		Entity server = this.datastoreService.prepare(query).asSingleEntity();

		this.datastoreService.delete(server.getKey());
		return serverId;
	}

	/**
	 * 
	 */
	@Override
	public List<ServerDTO> getServers(String identity) {

		List<ServerDTO> serverList = new ArrayList<ServerDTO>();

		Query query = new Query("CiServer");
		query.addFilter("federatedIdentity", FilterOperator.EQUAL, identity);

		List<Entity> entityResultList = this.datastoreService.prepare(query)
		        .asList(FetchOptions.Builder.withDefaults());

		for (Entity entity : entityResultList) {
			serverList.add(toDTO(entity));
		}

		return serverList;
	}

	/**
	 * 
	 */
	@Override
	public ServerDTO getServer(String identity, Long serverId) {
		Key serverKey = KeyFactory.createKey("CiServer", serverId.longValue());
		ServerDTO serverDTO = new ServerDTO();

		Query query = new Query("CiServer");
		query.addFilter("federatedIdentity", FilterOperator.EQUAL, identity).addFilter("__key__", FilterOperator.EQUAL,
		        serverKey);
		Entity server = this.datastoreService.prepare(query).asSingleEntity();
		serverDTO = toDTO(server);

		return serverDTO;
	}

	/**
	 * 
	 */
	@Override
	public ProjectsDTO getProjects(String identity, Long serverId) throws IOException {
		HTTPResponse httpResponse = this.get(identity, serverId, "rest/projects");
		return this.serialize(httpResponse.getContent(), ProjectsDTO.class);
	}

	/**
	 * 
	 */
	@Override
	public ProjectDTO getProject(String identity, Long serverId, String projectName) throws IOException {
		HTTPResponse httpResponse = this.get(identity, serverId, new StringBuilder("rest/projects/")
		        .append(projectName).toString());
		return this.serialize(httpResponse.getContent(), ProjectDTO.class);
	}

	/**
	 * 
	 */
	@Override
	public BuildsDTO getProjectBuilds(String identity, Long serverId, String projectName) throws IOException {
		HTTPResponse httpResponse = this.get(identity, serverId, new StringBuilder("rest/projects/")
		        .append(projectName).append("/builds").toString());
		return this.serialize(httpResponse.getContent(), BuildsDTO.class);
	}

	/**
	 * 
	 */
	@Override
	public BuildDTO getProjectBuild(String identity, Long serverId, String projectName, Long buildNumber)
	        throws IOException {
		HTTPResponse httpResponse = this.get(identity, serverId, new StringBuilder("rest/projects/")
		        .append(projectName).append("/").append(buildNumber).toString());
		return this.serialize(httpResponse.getContent(), BuildDTO.class);
	}

	/**
	 * 
	 */
	@Override
	public ConsoleDTO getProjectConsole(String identity, Long serverId, String projectName, Long buildNumber)
	        throws IOException {
		HTTPResponse httpResponse = this.get(identity, serverId, new StringBuilder("rest/projects/")
		        .append(projectName).append("/").append(buildNumber).append("/console").toString());
		return this.serialize(httpResponse.getContent(), ConsoleDTO.class);
	}

	/**
	 * 
	 */
	@Override
	public ConsoleContentDTO getProjectConsoleContent(String identity, Long serverId, String projectName,
	        Long buildNumber, Long offset, Long length) throws IOException {

		ConsoleContentDTO consoleContentDTO = new ConsoleContentDTO();
		consoleContentDTO.setContent("no content");

		if (length == null) {
			length = getBuildConsoleLength(identity, serverId, projectName, buildNumber);
		}

		if (offset == null) {
			offset = Long.valueOf(0);
		}

		HTTPResponse httpResponse = this.get(identity, serverId, new StringBuilder("rest/projects/")
		        .append(projectName).append("/").append(buildNumber).append("/console/content?").append("offset=")
		        .append(offset).append("&length=").append(length).toString());

		consoleContentDTO.setContent(new String(httpResponse.getContent()));

		return consoleContentDTO;
	}

	/**
	 * 
	 */
	@Override
	public Map<String, String> startProjectBuild(String identity, Long serverId, String projectName,
	        Map<String, String> buildParameter) throws IOException {

		if (buildParameter.values().isEmpty() == false) {
			StringBuilder queryParamter = getBuildParameter(buildParameter);
			HTTPResponse httpResponse = this.put(identity, serverId, new StringBuilder("job/").append(projectName)
			        .append("/buildWithParameters").append(queryParamter).toString(), new byte[0]);
		} else {
			HTTPResponse httpResponse = this.put(identity, serverId, new StringBuilder("job/").append(projectName)
			        .append("/build").toString(), new byte[0]);
		}

		return buildParameter;
	}

	/**
	 * 
	 */
	@Override
	public void stopProjectBuild(String identity, Long serverId, String projectName, Long buildNumber)
	        throws IOException {
		HTTPResponse httpResponse = this.put(identity, serverId, new StringBuilder("rest/projects/")
		        .append(projectName).append("/").append(buildNumber).append("/stop").toString(), new byte[0]);
	}

	/**
	 * 
	 */
	@Override
	public void deleteProject(String identity, Long serverId, String projectName) throws IOException {
		HTTPResponse httpResponse = this.post(identity, serverId,
		        new StringBuilder("job/").append(projectName).append("/doDelete").toString(), new byte[0], null);
	}

	/**
	 * 
	 * @param content
	 * @param clazz
	 * @return
	 * @throws JsonParseException
	 * @throws JsonMappingException
	 * @throws IOException
	 */
	private <T> T serialize(byte[] content, Class<T> clazz) throws JsonParseException, JsonMappingException,
	        IOException {
		String json = new String(content).replaceAll("@class", "clazz");
		return new ObjectMapper().readValue(json, clazz);
	}

	/**
	 * 
	 * @param serverId
	 * @param relativePath
	 * @return
	 * @throws IOException
	 */
	private HTTPResponse get(String identity, Long serverId, String relativePath) throws IOException {

		ServerDTO server = this.getServer(identity, serverId);

		HTTPRequest httpRequest = new HTTPRequest(new URL(new StringBuilder(server.getServerUrl()).append(relativePath)
		        .toString()), HTTPMethod.GET);

		setAuthenticationHeader(httpRequest, server.getUser(), server.getPassword());
		return this.urlFetchService.fetch(httpRequest);
	}

	/**
	 * 
	 * @param serverId
	 * @param relativePath
	 * @param payload
	 * @param contentType
	 * @return
	 * @throws IOException
	 */
	private HTTPResponse post(String identity, Long serverId, String relativePath, byte[] payload, String contentType)
	        throws IOException {
		ServerDTO server = this.getServer(identity, serverId);

		HTTPRequest httpRequest = new HTTPRequest(new URL(new StringBuilder(server.getServerUrl()).append(relativePath)
		        .toString()), HTTPMethod.POST);

		httpRequest.addHeader(new HTTPHeader("Content-length", String.valueOf(payload.length)));
		if (contentType != null) {
			httpRequest.addHeader(new HTTPHeader("Content-type", contentType));
		}
		httpRequest.setPayload(payload);
		setAuthenticationHeader(httpRequest, server.getUser(), server.getPassword());
		return this.urlFetchService.fetch(httpRequest);
	}

	/**
	 * 
	 * @param serverId
	 * @param relativePath
	 * @param payload
	 * @return
	 * @throws IOException
	 */
	private HTTPResponse put(String identity, Long serverId, String relativePath, byte[] payload) throws IOException {

		ServerDTO server = this.getServer(identity, serverId);

		HTTPRequest httpRequest = new HTTPRequest(new URL(new StringBuilder(server.getServerUrl()).append(relativePath)
		        .toString()), HTTPMethod.PUT);

		setAuthenticationHeader(httpRequest, server.getUser(), server.getPassword());
		httpRequest.setPayload(payload);

		return this.urlFetchService.fetch(httpRequest);
	}

	/**
	 * 
	 * @param httpRequest
	 * @param user
	 * @param password
	 */
	private void setAuthenticationHeader(HTTPRequest httpRequest, String user, String password) {
		httpRequest.addHeader(new HTTPHeader("Authorization", "Basic "
		        + Base64.encode((new StringBuilder(user).append(":").append(password)).toString().getBytes())));

	}

	/**
	 * 
	 * @param serverId
	 * @param projectName
	 * @param buildNumber
	 * @return
	 * @throws IOException
	 */
	private Long getBuildConsoleLength(String identity, Long serverId, String projectName, Long buildNumber)
	        throws IOException {
		Long length;
		ConsoleDTO projectConsole = this.getProjectConsole(identity, serverId, projectName, buildNumber);
		if (projectConsole != null) {
			length = projectConsole.getLength();
		} else {
			length = Long.valueOf(0);
		}
		return length;
	}

	/**
	 * 
	 * @param buildParameter
	 * @return
	 */
	private StringBuilder getBuildParameter(Map<String, String> buildParameter) {
		StringBuilder queryParamter = new StringBuilder("?");
		Iterator<Entry<String, String>> iterator = buildParameter.entrySet().iterator();
		while (iterator.hasNext()) {
			Map.Entry<String, String> entry = (Map.Entry<String, String>) iterator.next();
			queryParamter.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue())).append("&");
		}
		return queryParamter;
	}

	/**
	 * 
	 * @param serverEntity
	 * @return
	 */
	private ServerDTO toDTO(Entity serverEntity) {
		ServerDTO serverDTO = new ServerDTO();
		serverDTO.setKey(serverEntity.getKey().getId());
		serverDTO.setServerAlias(serverEntity.getProperty("ciServerAlias").toString());
		serverDTO.setPassword(serverEntity.getProperty("ciPassword").toString());
		serverDTO.setServerUrl(serverEntity.getProperty("ciServerUrl").toString());
		serverDTO.setUser(serverEntity.getProperty("ciUser").toString());
		serverDTO.setIdentity(serverEntity.getProperty("federatedIdentity").toString());
		return serverDTO;
	}

	/**
	 * 
	 * @param serverDTO
	 * @return
	 */
	private Entity toEntity(ServerDTO serverDTO) {
		Entity serverEntity = new Entity("CiServer");
		serverEntity.setProperty("ciServerAlias", serverDTO.getServerAlias());
		serverEntity.setProperty("ciPassword", serverDTO.getPassword());
		serverEntity.setProperty("ciServerUrl", serverDTO.getServerUrl());
		serverEntity.setProperty("ciUser", serverDTO.getUser());
		serverEntity.setProperty("federatedIdentity", serverDTO.getIdentity());
		return serverEntity;
	}

}
